#ifndef STATE_MANAGER_H
#define STATE_MANAGER_H

#include "Exception.h"
#include "Timer.h"
#include "VideoDevice.h"
#include "Renderer.h"
#include "Input.h"
#include "Engine.h"

namespace Cvekas {

class StateManager;

/// %Application state
class State
{
public:
	/// Constructor
	State(RendererPtr main_renderer)
		: main_renderer(main_renderer)
	{
		Engine e;
		video_device = e.getVideoDevice();
		input = e.getInput();
	}

	/// Called when this state is passed to StateManager::setState
	virtual void init() {};
	/// Called on StateManager destruction
	virtual void close() {};

	/// Called before making this state active
	virtual void activate(const StateManager& state_manager) {};
	/// Called before making other state active
	virtual void deactivate(const StateManager& state_manager) {};

	/// Called once per frame, before rendering
	virtual void update(const StateManager& state_manager) {};
	/// Called once per frame. All rendering must happen here.
	virtual void render(const StateManager& state_manager) = 0;
	/// Called when fading this state in/out. 
	/// \param state_manager StateManager responsible for this state
	/// \param amount Number in range 0.0 - 1.0. Should be used as transition coefficient. 
	/// \param from ID of state which is faded out
	/// \param to ID of state which is faded in
	virtual void fade(const StateManager& state_manager, float amount, uint from, uint to) = 0;

protected:
	VideoDevicePtr video_device;
	RendererPtr main_renderer;
	InputPtr input;
};

typedef boost::shared_ptr<State> StatePtr;

/// Manages State objects
class StateManager : public boost::noncopyable
{
public:
	/// Constructor
	/// \param state_count Number of states to manage. This is constant and cannot change!
	/// \param fade_speed Amount of time in seconds how long transition from one state to another will last. 
	///	Can pass 0 for no transitions
	StateManager(uint state_count, float fade_speed = 1.0f)
		: state_count(state_count), fade_speed(fade_speed)
	{
		StatePtr dummy;
		states.assign(state_count, dummy);
	}

	/// Destructor
	~StateManager()
	{
		for(uint i = 0; i < state_count; ++i)
		{
			if(states[i])
				states[i]->close();
		}
	}

	/// Assigns state to id number
	/// \param id Identifier, must be in range 0 - state_count-1
	/// \param state State to assign
	void setState(uint id, StatePtr state)
	{
		if(id >= state_count)
			throw EXCEPTION("Invalid state id!");
		
		states[id] = state;
		state->init();
	}
	
	/// Returns state with provided id
	StatePtr getState(uint id) const
	{
		if(id >= state_count)
			throw EXCEPTION("Invalid state id!");

		return states[id];
	}

	/// Returns active state id or -1 if no state is active (e.g. transition)
	uint getActiveStateId() const
	{
		return active_state;
	}

	/// Makes state with provided id active, pushes it onto stack.
	void pushState(uint id) const
	{
		if(!states[id])
			throw EXCEPTION("Trying to push invalid state onto stack!");

		fade_from = state_stack.empty() ? ~0 : state_stack.top();
		state_stack.push(id);
		fade_to = id;

		if(fade_from != ~0)
			states[fade_from]->deactivate(*this);

		if(state_stack.size() > 1)
		{
			active_state = state_fading;
			fade_start = Timer::getTimeStamp();
		}
		else
		{
			active_state = id;
			states[id]->activate(*this);
		}
	}

	/// Pops topmost state from stack, makes one below it active.
	void popState() const
	{
		states[state_stack.top()]->deactivate(*this);
		if(state_stack.size() > 1)
		{
			fade_from = state_stack.top();
			state_stack.pop();
			fade_to = state_stack.top();
			active_state = state_fading;
			fade_start = Timer::getTimeStamp();
		}
		else
		{
			state_stack.pop();
		}
	}

	/// Swaps top of stack with provided state.
	void switchState(uint id) const
	{
		if(!states[id])
			throw EXCEPTION("Trying to switch to invalid state!");

		states[state_stack.top()]->deactivate(*this);

		fade_from = state_stack.top();
		fade_to = id;
		state_stack.pop();
		state_stack.push(id);
		active_state = state_fading;
		fade_start = Timer::getTimeStamp();
	}

	/// Calls update function of active state (if not in transition).
	/// \returns false if state stack is empty, true otherwise.
	bool update()
	{
		if(state_stack.empty())
			return false;

		if(active_state != state_fading)
		{
			states[active_state]->update(*this);
		}

		return true;
	}

	/// Calls render function of active state (if not in transition), or fade functions of two fading states.
	/// \returns false if state stack is empty, true otherwise.
	bool render()
	{
		if(state_stack.empty())
			return false;

		if(active_state == state_fading)
		{
			TimeStamp diff = Timer::getTimeStamp() - fade_start;
			float amount = static_cast<float>(diff.toMiliSec()) / (fade_speed * 1000.0f);

			if(amount > 1.0f)
			{
				active_state = fade_to;
				states[active_state]->activate(*this);
				states[active_state]->render(*this);
				return true;
			}

			states[fade_from]->fade(*this, 1.0f - amount, ~0, fade_to);
			states[fade_to]->fade(*this, amount, fade_from, ~0);
			return true;
		}

		states[active_state]->render(*this);
		return true;
	}

private:
	static const uint state_fading = ~0;
	const float fade_speed;
	const uint state_count;
	
	std::vector<StatePtr> states;
	mutable std::stack<uint> state_stack;
	mutable uint active_state, fade_to, fade_from;
	mutable TimeStamp fade_start;
};

} // namespace

#endif